Passed
Push — master ( b88a18...620cf7 )
by EMP
05:44
created

main.js ➔ addAccountToTable   B

Complexity

Conditions 6

Size

Total Lines 27
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 6
eloc 21
dl 0
loc 27
rs 8.4426
c 0
b 0
f 0
1
"use strict";
2
3
sodium.ready.then(function() {
4
5
const ae = new AllEars(function(ok) {
6
	if (ok) {
7
		if (localStorage.greeting) {
8
			document.getElementById("greeting").textContent = localStorage.greeting;
9
			document.getElementById("txt_pg").value = localStorage.greeting;
10
		} else localStorage.greeting = document.getElementById("greeting").textContent;
11
12
		document.getElementById("txt_skey").style.background = "#466";
13
		document.getElementById("txt_skey").maxLength = "64";
14
	} else {
15
		console.log("Failed to load All-Ears");
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
16
	}
17
});
18
19
function TabState(cur, max, btnDele, btnUpdt) {
20
	this.cur = cur;
21
	this.max = max;
22
	this.btnDele = btnDele;
23
	this.btnUpdt = btnUpdt;
24
}
25
26
const tabs = [
27
	new TabState(0, 0, false, true), // Inbox
28
	new TabState(0, 0, false, true), // Outbx
29
	new TabState(0, 1, true, false), // Write
30
	new TabState(0, 2, false, false), // Notes
31
	new TabState(0, 2, false, true) // Tools
32
];
33
34
let showHeaders = false;
35
36
let tab = 0;
37
const TAB_INBOX = 0;
38
const TAB_DRBOX = 1;
39
const TAB_WRITE = 2;
40
const TAB_NOTES = 3;
41
const TAB_TOOLS = 4;
42
43
// Helper functions
44
function getCountryName(countryCode) {
45
	switch (countryCode) {
46
		case "??": return "Unknown";
47
		case "DZ": return "Algeria";
48
		case "AO": return "Angola";
49
		case "BJ": return "Benin";
50
		case "BW": return "Botswana";
51
		case "BF": return "Burkina Faso";
52
		case "BI": return "Burundi";
53
		case "CV": return "Cabo Verde";
54
		case "CM": return "Cameroon";
55
		case "CF": return "Central African Republic";
56
		case "TD": return "Chad";
57
		case "KM": return "Comoros";
58
		case "CD": return "Congo";
59
		case "DJ": return "Djibouti";
60
		case "EG": return "Egypt";
61
		case "GQ": return "Equatorial Guinea";
62
		case "ER": return "Eritrea";
63
		case "SZ": return "Eswatini";
64
		case "ET": return "Ethiopia";
65
		case "GA": return "Gabon";
66
		case "GM": return "Gambia";
67
		case "GH": return "Ghana";
68
		case "GW": return "Guinea-Bissau";
69
		case "GN": return "Guinea";
70
		case "CI": return "Ivory Coast";
71
		case "KE": return "Kenya";
72
		case "LS": return "Lesotho";
73
		case "LR": return "Liberia";
74
		case "LY": return "Libya";
75
		case "MG": return "Madagascar";
76
		case "MW": return "Malawi";
77
		case "ML": return "Mali";
78
		case "MR": return "Mauritania";
79
		case "MU": return "Mauritius";
80
		case "YT": return "Mayotte";
81
		case "MA": return "Morocco";
82
		case "MZ": return "Mozambique";
83
		case "NA": return "Namibia";
84
		case "NE": return "Niger";
85
		case "NG": return "Nigeria";
86
		case "CG": return "Republic of the Congo";
87
		case "RW": return "Rwanda";
88
		case "RE": return "Réunion";
89
		case "SH": return "Saint Helena";
90
		case "SN": return "Senegal";
91
		case "SC": return "Seychelles";
92
		case "SL": return "Sierra Leone";
93
		case "SO": return "Somalia";
94
		case "ZA": return "South Africa";
95
		case "SS": return "South Sudan";
96
		case "SD": return "Sudan";
97
		case "ST": return "São Tomé and Príncipe";
98
		case "TZ": return "Tanzania";
99
		case "TG": return "Togo";
100
		case "TN": return "Tunisia";
101
		case "UG": return "Uganda";
102
		case "EH": return "Western Sahara";
103
		case "ZM": return "Zambia";
104
		case "ZW": return "Zimbabwe";
105
		case "AQ": return "Antarctica";
106
		case "BV": return "Bouvet Island";
107
		case "TF": return "French Southern Territories";
108
		case "HM": return "Heard Island and McDonald Islands";
109
		case "GS": return "South Georgia and the South Sandwich Islands";
110
		case "AF": return "Afghanistan";
111
		case "AM": return "Armenia";
112
		case "AZ": return "Azerbaijan";
113
		case "BH": return "Bahrain";
114
		case "BD": return "Bangladesh";
115
		case "BT": return "Bhutan";
116
		case "IO": return "British Indian Ocean Territory";
117
		case "BN": return "Brunei";
118
		case "KH": return "Cambodia";
119
		case "CN": return "China";
120
		case "CC": return "Cocos [Keeling] Islands";
121
		case "GE": return "Georgia";
122
		case "JO": return "Hashemite Kingdom of Jordan";
123
		case "HK": return "Hong Kong";
124
		case "IN": return "India";
125
		case "ID": return "Indonesia";
126
		case "IR": return "Iran";
127
		case "IQ": return "Iraq";
128
		case "IL": return "Israel";
129
		case "JP": return "Japan";
130
		case "KZ": return "Kazakhstan";
131
		case "KW": return "Kuwait";
132
		case "KG": return "Kyrgyzstan";
133
		case "LA": return "Laos";
134
		case "LB": return "Lebanon";
135
		case "MO": return "Macao";
136
		case "MY": return "Malaysia";
137
		case "MV": return "Maldives";
138
		case "MN": return "Mongolia";
139
		case "MM": return "Myanmar";
140
		case "NP": return "Nepal";
141
		case "KP": return "North Korea";
142
		case "OM": return "Oman";
143
		case "PK": return "Pakistan";
144
		case "PS": return "Palestine";
145
		case "PH": return "Philippines";
146
		case "QA": return "Qatar";
147
		case "SA": return "Saudi Arabia";
148
		case "SG": return "Singapore";
149
		case "KR": return "South Korea";
150
		case "LK": return "Sri Lanka";
151
		case "SY": return "Syria";
152
		case "TW": return "Taiwan";
153
		case "TJ": return "Tajikistan";
154
		case "TH": return "Thailand";
155
		case "TR": return "Turkey";
156
		case "TM": return "Turkmenistan";
157
		case "AE": return "United Arab Emirates";
158
		case "UZ": return "Uzbekistan";
159
		case "VN": return "Vietnam";
160
		case "YE": return "Yemen";
161
		case "AL": return "Albania";
162
		case "AD": return "Andorra";
163
		case "AT": return "Austria";
164
		case "BY": return "Belarus";
165
		case "BE": return "Belgium";
166
		case "BA": return "Bosnia and Herzegovina";
167
		case "BG": return "Bulgaria";
168
		case "HR": return "Croatia";
169
		case "CY": return "Cyprus";
170
		case "CZ": return "Czechia";
171
		case "DK": return "Denmark";
172
		case "EE": return "Estonia";
173
		case "FO": return "Faroe Islands";
174
		case "FI": return "Finland";
175
		case "FR": return "France";
176
		case "DE": return "Germany";
177
		case "GI": return "Gibraltar";
178
		case "GR": return "Greece";
179
		case "GG": return "Guernsey";
180
		case "HU": return "Hungary";
181
		case "IS": return "Iceland";
182
		case "IE": return "Ireland";
183
		case "IM": return "Isle of Man";
184
		case "IT": return "Italy";
185
		case "JE": return "Jersey";
186
		case "XK": return "Kosovo";
187
		case "LV": return "Latvia";
188
		case "LI": return "Liechtenstein";
189
		case "LU": return "Luxembourg";
190
		case "MT": return "Malta";
191
		case "MC": return "Monaco";
192
		case "ME": return "Montenegro";
193
		case "NL": return "Netherlands";
194
		case "MK": return "North Macedonia";
195
		case "NO": return "Norway";
196
		case "PL": return "Poland";
197
		case "PT": return "Portugal";
198
		case "LT": return "Republic of Lithuania";
199
		case "MD": return "Republic of Moldova";
200
		case "RO": return "Romania";
201
		case "RU": return "Russia";
202
		case "SM": return "San Marino";
203
		case "RS": return "Serbia";
204
		case "SK": return "Slovakia";
205
		case "SI": return "Slovenia";
206
		case "ES": return "Spain";
207
		case "SJ": return "Svalbard and Jan Mayen";
208
		case "SE": return "Sweden";
209
		case "CH": return "Switzerland";
210
		case "UA": return "Ukraine";
211
		case "GB": return "United Kingdom";
212
		case "VA": return "Vatican City";
213
		case "AX": return "Åland";
214
		case "AI": return "Anguilla";
215
		case "AG": return "Antigua and Barbuda";
216
		case "AW": return "Aruba";
217
		case "BS": return "Bahamas";
218
		case "BB": return "Barbados";
219
		case "BZ": return "Belize";
220
		case "BM": return "Bermuda";
221
		case "BQ": return "Bonaire, Sint Eustatius, and Saba";
222
		case "VG": return "British Virgin Islands";
223
		case "CA": return "Canada";
224
		case "KY": return "Cayman Islands";
225
		case "CR": return "Costa Rica";
226
		case "CU": return "Cuba";
227
		case "CW": return "Curaçao";
228
		case "DM": return "Dominica";
229
		case "DO": return "Dominican Republic";
230
		case "SV": return "El Salvador";
231
		case "GL": return "Greenland";
232
		case "GD": return "Grenada";
233
		case "GP": return "Guadeloupe";
234
		case "GT": return "Guatemala";
235
		case "HT": return "Haiti";
236
		case "HN": return "Honduras";
237
		case "JM": return "Jamaica";
238
		case "MQ": return "Martinique";
239
		case "MX": return "Mexico";
240
		case "MS": return "Montserrat";
241
		case "NI": return "Nicaragua";
242
		case "PA": return "Panama";
243
		case "PR": return "Puerto Rico";
244
		case "BL": return "Saint Barthélemy";
245
		case "LC": return "Saint Lucia";
246
		case "MF": return "Saint Martin";
247
		case "PM": return "Saint Pierre and Miquelon";
248
		case "VC": return "Saint Vincent and the Grenadines";
249
		case "SX": return "Sint Maarten";
250
		case "KN": return "St Kitts and Nevis";
251
		case "TT": return "Trinidad and Tobago";
252
		case "TC": return "Turks and Caicos Islands";
253
		case "VI": return "U.S. Virgin Islands";
254
		case "US": return "United States";
255
		case "AS": return "American Samoa";
256
		case "AU": return "Australia";
257
		case "CX": return "Christmas Island";
258
		case "CK": return "Cook Islands";
259
		case "TL": return "Democratic Republic of Timor-Leste";
260
		case "FM": return "Federated States of Micronesia";
261
		case "FJ": return "Fiji";
262
		case "PF": return "French Polynesia";
263
		case "GU": return "Guam";
264
		case "KI": return "Kiribati";
265
		case "MH": return "Marshall Islands";
266
		case "NR": return "Nauru";
267
		case "NC": return "New Caledonia";
268
		case "NZ": return "New Zealand";
269
		case "NU": return "Niue";
270
		case "NF": return "Norfolk Island";
271
		case "MP": return "Northern Mariana Islands";
272
		case "PW": return "Palau";
273
		case "PG": return "Papua New Guinea";
274
		case "PN": return "Pitcairn Islands";
275
		case "WS": return "Samoa";
276
		case "SB": return "Solomon Islands";
277
		case "TK": return "Tokelau";
278
		case "TO": return "Tonga";
279
		case "TV": return "Tuvalu";
280
		case "UM": return "U.S. Minor Outlying Islands";
281
		case "VU": return "Vanuatu";
282
		case "WF": return "Wallis and Futuna";
283
		case "AR": return "Argentina";
284
		case "BO": return "Bolivia";
285
		case "BR": return "Brazil";
286
		case "CL": return "Chile";
287
		case "CO": return "Colombia";
288
		case "EC": return "Ecuador";
289
		case "FK": return "Falkland Islands";
290
		case "GF": return "French Guiana";
291
		case "GY": return "Guyana";
292
		case "PY": return "Paraguay";
293
		case "PE": return "Peru";
294
		case "SR": return "Suriname";
295
		case "UY": return "Uruguay";
296
		case "VE": return "Venezuela";
297
	}
298
299
	return "Error";
300
}
301
302
function getCountryFlag(countryCode) {
303
	return (!countryCode || countryCode.length !== 2 || countryCode == "??") ? "❔" : sodium.to_string(new Uint8Array([
304
		240, 159, 135, 166 + countryCode.codePointAt(0) - 65,
305
		240, 159, 135, 166 + countryCode.codePointAt(1) - 65
306
	]));
307
}
308
309
function getClockIcon(d) {
310
	const h24 = d.getUTCHours();
311
	let h12 = (h24 === 0 ? 12 : ((h24 > 12) ? h24 - 12 : h24));
312
313
	const m60 = (d.getUTCMinutes() * 60) + d.getUTCSeconds();
314
	let m30 = 0;
315
	if (m60 <= 900) { // <= 15: round down to this hour
316
		m30 = 0;
317
	} else if (m60 > 900 && m60 < 2700) { // 15..45: round to half-past this hour
318
		m30 = 12;
319
	} else { // >= 45: round up to next hour
320
		h12++;
321
		m30 = 0;
322
	}
323
324
	return "&#" + ((128335 + h12) + m30) + ";";
325
}
326
327
function clearDisplay() {
328
	let el = document.getElementById("midright").getElementsByTagName("img");
329
	if (el.length !== 1) el = document.getElementById("midright").getElementsByTagName("audio");
330
	if (el.length !== 1) el = document.getElementById("midright").getElementsByTagName("video");
331
	if (el.length !== 1) el = document.getElementById("midright").getElementsByTagName("embed");
332
	if (el.length !== 1) el = document.getElementById("midright").getElementsByTagName("iframe");
333
	if (el.length !== 1) return;
334
335
	URL.revokeObjectURL(el[0].src);
336
	el[0].remove();
337
}
338
339
function downloadFile(num) {
340
	const a = document.createElement("a");
341
	a.href = URL.createObjectURL(new Blob([ae.GetUplMsgBody(num).buffer]));
342
	a.download = ae.GetUplMsgTitle(num);
343
	a.click();
344
345
	URL.revokeObjectURL(a.href);
346
	a.href = "";
347
	a.download = "";
348
}
349
350
function displayFile(num) {
351
	const fileType = ae.GetUplMsgType(num);
352
	if (!fileType) return downloadFile(num);
353
354
	clearDisplay();
355
	document.getElementById("midright").scroll(0, 0);
356
	document.getElementById("midright").setAttribute("data-msgid", ae.GetUplMsgIdHex(num));
357
358
	document.getElementById("btn_mdele").disabled = false;
359
	document.getElementById("btn_msave").disabled = false;
360
	document.getElementById("btn_reply").disabled = true;
361
362
	document.getElementById("btn_msave").onclick = function() {downloadFile(num);}
363
364
	document.getElementById("midright").children[0].hidden = true;
365
	document.getElementById("midright").children[1].textContent = ae.GetUplMsgTitle(num);
366
367
	switch (fileType) {
368
		case "text": {
369
			document.getElementById("midright").children[2].hidden = false;
370
			document.getElementById("midright").children[2].textContent = sodium.to_string(ae.GetUplMsgBody(num));
371
		break;}
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
372
373
		case "image": {
374
			document.getElementById("midright").children[2].hidden = true;
375
			const img = document.createElement("img");
376
			img.src = URL.createObjectURL(new Blob([ae.GetUplMsgBody(num).buffer]));
377
			document.getElementById("midright").appendChild(img);
378
379
			img.onclick = function() {
380
				if (!document.fullscreen)
381
					img.requestFullscreen();
382
				else
383
					document.exitFullscreen();
384
			};
385
		break;}
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
386
387
		case "audio": {
388
			document.getElementById("midright").children[2].hidden = true;
389
			const el = document.createElement("audio");
390
			el.controls = "controls";
391
			el.src = URL.createObjectURL(new Blob([ae.GetUplMsgBody(num).buffer]));
392
			document.getElementById("midright").appendChild(el);
393
		break;}
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
394
395
		case "video": {
396
			document.getElementById("midright").children[2].hidden = true;
397
			const el = document.createElement("video");
398
			el.controls = "controls";
399
			el.src = URL.createObjectURL(new Blob([ae.GetUplMsgBody(num).buffer]));
400
			document.getElementById("midright").appendChild(el);
401
		break;}
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
402
403
		case "pdf": {
404
			document.getElementById("midright").children[2].hidden = true;
405
			const el = document.createElement("embed");
406
			el.type = "application/pdf";
407
			el.src = URL.createObjectURL(new Blob([ae.GetUplMsgBody(num).buffer], {type: "application/pdf"}));
408
			document.getElementById("midright").appendChild(el);
409
		break;}
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
410
411
		case "html": {
412
			document.getElementById("midright").children[2].hidden = true;
413
			const el = document.createElement("iframe");
414
			el.allow = "";
415
			el.sandbox = "";
416
			el.referrerPolicy = "no-referrer";
417
			el.csp = "base-uri 'none'; child-src 'none'; connect-src 'none'; default-src 'none'; font-src 'none'; form-action 'none'; frame-ancestors 'none'; frame-src 'none'; img-src 'none'; manifest-src 'none'; media-src 'none'; object-src 'none'; script-src 'none'; style-src 'none'; worker-src 'none';";
418
			el.srcdoc = sodium.to_string(ae.GetUplMsgBody(num).buffer);
419
			document.getElementById("midright").appendChild(el);
420
		break;}
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
421
	}
0 ignored issues
show
Comprehensibility introduced by
There is no default case in this switch, so nothing gets returned when all cases fail. You might want to consider adding a default or return undefined explicitly.
Loading history...
422
}
423
424
function displayMsg(isInt, num) {
425
	clearDisplay();
426
427
	document.getElementById("btn_mdele").disabled = false;
428
	document.getElementById("btn_msave").disabled = isInt;
429
430
	document.getElementById("midright").scroll(0, 0);
431
	document.getElementById("midright").setAttribute("data-msgid", isInt? ae.GetIntMsgIdHex(num) : ae.GetExtMsgIdHex(num));
432
433
	const ts = isInt? ae.GetIntMsgTime(num) : ae.GetExtMsgTime(num);
434
435
	if (!isInt) {
436
		document.getElementById("btn_msave").onclick = function() {
437
			this.blur();
438
439
			const a = document.createElement("a");
440
			a.href = URL.createObjectURL(new Blob([ae.ExportExtMsg(num)]));
441
			a.download = ae.GetExtMsgTitle(num);
442
			a.click();
443
444
			URL.revokeObjectURL(a.href);
445
			a.href = "";
446
			a.download = "";
447
		}
448
	}
449
450
	if (!isInt || (ae.GetIntMsgFrom(num) !== "public" && ae.GetIntMsgFrom(num) !== "system")) {
451
		document.getElementById("btn_reply").disabled = false;
452
453
		document.getElementById("btn_reply").onclick = function() {
454
			document.getElementById("write_recv").value = isInt? ae.GetIntMsgFrom(num) : ae.GetExtMsgReplyAddress(num);
455
			document.getElementById("write_subj").value = isInt? ae.GetIntMsgTitle(num) : ae.GetExtMsgTitle(num);
456
			if (!document.getElementById("write_subj").value.startsWith("Re:")) document.getElementById("write_subj").value = "Re: " + document.getElementById("write_subj").value;
457
			document.querySelector("#write2_pkey > input").value = isInt? ae.GetIntMsgFromPk(num) : "";
458
459
			document.getElementById("write_recv").readOnly = !isInt;
460
			document.getElementById("write_subj").readOnly = !isInt;
461
			document.getElementById("write_subj").setAttribute("data-replyid", isInt? "" : ae.GetExtMsgHdrId(num));
462
463
			tabs[TAB_WRITE].cur = 0;
464
			document.getElementById("btn_write").disabled = false;
465
			document.getElementById("btn_write").click();
466
			document.getElementById("write_body").focus();
467
468
			for (const opt of document.getElementById("write_from").options) {
469
				if (opt.value === (isInt ? ae.GetIntMsgTo(num) : ae.GetExtMsgEnvTo(num))) {
470
					opt.selected = true;
471
				}
472
			}
473
		};
474
	} else {
475
		document.getElementById("btn_reply").disabled = true;
476
	}
477
478
	document.getElementById("midright").children[0].hidden = false;
479
	document.getElementById("midright").children[2].hidden = false;
480
481
	document.getElementById("readmsg_envto").textContent = isInt ? "" : ae.GetExtMsgEnvTo(num);
482
	document.getElementById("readmsg_hdrto").textContent = isInt ? ae.GetIntMsgTo(num) : (ae.GetExtMsgHdrTo(num) + (ae.GetExtMsgDnTo(num) ? " (" + ae.GetExtMsgDnTo(num) + ")" : ""));
483
484
	const tzOs = new Date().getTimezoneOffset();
485
	const msgDate = new Date((ts * 1000) + (tzOs * -60000));
486
	document.getElementById("readmsg_date").children[0].innerHTML = getClockIcon(msgDate);
487
	document.getElementById("readmsg_date").children[1].dateTime = new Date(ts * 1000).toISOString();
488
489
	if (isInt) {
490
		document.getElementById("midright").children[1].textContent = ae.GetIntMsgTitle(num);
491
		document.getElementById("midright").children[2].textContent = ae.GetIntMsgBody(num);
492
493
		document.getElementById("readmsg_date").children[1].textContent = msgDate.toISOString().slice(0, 19).replace("T", " ");
494
495
		document.getElementById("readmsg_ip").style.visibility = "hidden";
496
		document.getElementById("readmsg_rdns").style.visibility = "hidden";
497
		document.getElementById("readmsg_dkim").style.visibility = "hidden";
498
		document.getElementById("readmsg_greet").style.visibility = "hidden";
499
		document.getElementById("readmsg_cert").style.visibility = "hidden";
500
		document.getElementById("readmsg_envfrom").style.visibility = "hidden";
501
		document.getElementById("readmsg_envto").style.visibility = "hidden";
502
503
		if (ae.GetIntMsgFrom(num) !== "system" && ae.GetIntMsgFrom(num) !== "public") {
504
			document.getElementById("readmsg_tls").style.visibility = "visible";
505
			document.getElementById("readmsg_tls").children[0].textContent = ae.GetIntMsgFromPk(num);
506
		} else document.getElementById("readmsg_tls").style.visibility = "hidden";
507
508
		let symbol = "<span title=\"Invalid level\">&#x26a0;</span>";
509
		if      (ae.GetIntMsgFrom(num) === "system") {if (ae.GetIntMsgLevel(num) === 3) symbol = "<span title=\"System message\">&#x1f162;</span>";} // (S)
510
		else if (ae.GetIntMsgFrom(num) === "public") {if (ae.GetIntMsgLevel(num) === 3) symbol = "<span title=\"Public announcement\">&#x1f15f;</span>";} // (P)
511
		else if (ae.GetIntMsgLevel(num) === 0) symbol = "<span title=\"Level 0 User\">&#x1f10c;</span>"; // 0
512
		else if (ae.GetIntMsgLevel(num) === 1) symbol = "<span title=\"Level 1 User\">&#x278a;</span>"; // 1
513
		else if (ae.GetIntMsgLevel(num) === 2) symbol = "<span title=\"Level 2 User\">&#x278b;</span>"; // 2
514
		else if (ae.GetIntMsgLevel(num) === 3) symbol = "<span title=\"Administrator\">&#x1f150;</span>"; // A (Admin)
515
		document.getElementById("readmsg_hdrfrom").innerHTML = symbol + " " + ae.GetIntMsgFrom(num);
516
517
		let flagText = "";
518
		if (!ae.GetIntMsgFlagVPad(num)) flagText += "<abbr title=\"Invalid padding\">PAD</abbr> ";
519
		if (!ae.GetIntMsgFlagVSig(num)) flagText += "<abbr title=\"Invalid signature\">SIG</abbr> ";
520
		if (ae.GetIntMsgFlagE2ee(num)) flagText += "<abbr title=\"End-to-end encrypted\">E2EE</abbr> ";
521
		document.getElementById("readmsg_flags").children[0].innerHTML = flagText.trim();
522
	} else {
523
		document.getElementById("midright").children[2].innerHTML = "";
524
525
		const headers = document.createElement("p");
526
		headers.textContent = ae.GetExtMsgHeaders(num);
527
		headers.className = "mono";
528
		headers.hidden = !showHeaders;
529
		document.getElementById("midright").children[2].appendChild(headers);
530
531
		const certText = document.getElementById("readmsg_cert");
532
533
		let names = [];
534
		if (ae.GetExtMsgTls_MatchGreeting(num)) names.push(ae.GetExtMsgGreet(num));
535
		if (ae.GetExtMsgTls_MatchRdns(num))     names.push(ae.GetExtMsgRdns(num));
536
		if (ae.GetExtMsgTls_MatchEnvFrom(num))  names.push(ae.GetExtMsgEnvFrom(num).split("@")[1]);
537
		if (ae.GetExtMsgTls_MatchHdrFrom(num))  names.push(ae.GetExtMsgHdrFrom(num).split("@")[1]);
538
		names = [...new Set(names)];
539
540
		certText.children[0].textContent = (names[0]) ? (names.join(" ") + " " + ae.GetExtMsgTls_CertType(num)).trim() : ae.GetExtMsgTls_CertType(num);
541
542
		const body = document.createElement("p");
543
		body.textContent = ae.GetExtMsgBody(num);
544
		document.getElementById("midright").children[2].appendChild(body);
545
546
		document.getElementById("midright").children[1].textContent = ae.GetExtMsgTitle(num);
547
		document.getElementById("midright").children[1].onclick = function() {showHeaders = !showHeaders; headers.hidden = !showHeaders;};
548
		document.getElementById("midright").children[1].style.cursor = "pointer";
549
550
		let hdrSecs = Math.abs(ae.GetExtMsgHdrTime(num));
551
		let hdrTime = "";
552
		if (hdrSecs >= 3600) {
553
			const hdrHours = Math.floor(hdrSecs / 3600);
554
			hdrTime += hdrHours.toString() + "h ";
555
			hdrSecs -= hdrHours * 3600;
556
		}
557
		if (hdrSecs >= 60) {
558
			const hdrMins = Math.floor(hdrSecs / 60);
559
			hdrTime += hdrMins.toString() + "m ";
560
			hdrSecs -= hdrMins * 60;
561
		}
562
		hdrTime += hdrSecs + "s";
563
564
		const hdrTz = (ae.GetExtMsgHdrTz(num) >= 0 ? "+" : "-") + Math.floor(Math.abs(ae.GetExtMsgHdrTz(num)) / 60).toString().padStart(2, "0") + (Math.abs(ae.GetExtMsgHdrTz(num)) % 60).toString().padStart(2, "0");
565
		document.getElementById("readmsg_date").children[1].textContent = msgDate.toISOString().slice(0, 19).replace("T", " ") + "; " + hdrTz + " " + ((ae.GetExtMsgHdrTime(num) >= 0) ? "+" : "-") + hdrTime;
566
567
		document.getElementById("readmsg_ip").style.visibility = "visible";
568
		document.getElementById("readmsg_rdns").style.visibility = "visible";
569
		document.getElementById("readmsg_dkim").style.visibility = "visible";
570
		document.getElementById("readmsg_greet").style.visibility = "visible";
571
		document.getElementById("readmsg_tls").style.visibility = "visible";
572
		document.getElementById("readmsg_cert").style.visibility = "visible";
573
		document.getElementById("readmsg_envfrom").style.visibility = "visible";
574
		document.getElementById("readmsg_envto").style.visibility = "visible";
575
576
		const cc = ae.GetExtMsgCountry(num);
577
578
		document.getElementById("readmsg_country").textContent = getCountryFlag(cc);
579
		document.getElementById("readmsg_country").title = getCountryName(cc);
580
		document.getElementById("readmsg_ip").children[1].textContent = ae.GetExtMsgIp(num);
581
		document.getElementById("readmsg_rdns").children[0].textContent = ae.GetExtMsgRdns(num);
582
		document.getElementById("readmsg_tls").children[0].textContent = ae.GetExtMsgTLS(num);
583
		document.getElementById("readmsg_greet").children[0].textContent = ae.GetExtMsgGreet(num);
584
		document.getElementById("readmsg_envfrom").textContent = ae.GetExtMsgEnvFrom(num);
585
		document.getElementById("readmsg_hdrfrom").textContent = ae.GetExtMsgHdrFrom(num) + (ae.GetExtMsgDnFrom(num) ? " (" + ae.GetExtMsgDnFrom(num) + ")" : "");
586
587
		let flagText = "";
588
		if (!ae.GetExtMsgFlagVPad(num)) flagText += "<abbr title=\"Invalid padding\">PAD</abbr> ";
589
		if (!ae.GetExtMsgFlagVSig(num)) flagText += "<abbr title=\"Invalid signature\">SIG</abbr> ";
590
		if (!ae.GetExtMsgFlagPExt(num)) flagText += "<abbr title=\"The sender did not use the Extended (ESMTP) protocol\">SMTP</abbr> ";
591
		if (!ae.GetExtMsgFlagQuit(num)) flagText += "<abbr title=\"The sender did not issue the required QUIT command\">QUIT</abbr> ";
592
		if (ae.GetExtMsgFlagRare(num)) flagText += "<abbr title=\"The sender issued unusual command(s)\">RARE</abbr> ";
593
		if (ae.GetExtMsgFlagFail(num)) flagText += "<abbr title=\"The sender issued invalid command(s)\">FAIL</abbr> ";
594
		if (ae.GetExtMsgFlagPErr(num)) flagText += "<abbr title=\"The sender violated the protocol\">PROT</abbr> ";
595
		if (ae.GetExtMsgFlagMult(num)) flagText += "<abbr title=\"Multiple recipients on this service\">MULT</abbr> ";
596
		document.getElementById("readmsg_flags").children[0].innerHTML = flagText.trim();
597
	}
598
}
599
600
// Interface
601
function addMsg(isInt, i) {
602
	const row = document.getElementById("tbl_inbox").insertRow(-1);
603
	row.setAttribute("data-msgid", isInt? ae.GetIntMsgIdHex(i) : ae.GetExtMsgIdHex(i));
604
605
	const ts = isInt? ae.GetIntMsgTime(i) : ae.GetExtMsgTime(i);
606
	const el = document.createElement("time");
607
	el.dateTime = new Date(ts * 1000).toISOString();
608
	el.textContent = new Date((ts * 1000) + (new Date().getTimezoneOffset() * -60000)).toISOString().slice(0, 10);
609
610
	const cellTime = row.insertCell(-1);
611
	cellTime.appendChild(el);
612
613
	const cellSubj = row.insertCell(-1);
614
	cellSubj.textContent = isInt? ae.GetIntMsgTitle(i) : ae.GetExtMsgTitle(i);
615
616
	if (isInt) {
617
		const cellSnd = row.insertCell(-1);
618
		cellSnd.textContent = ae.GetIntMsgFrom(i);
619
		cellSnd.className = (ae.GetIntMsgFrom(i).length === 16) ? "mono" : "";
620
	} else {
621
		const from1 = ae.GetExtMsgEnvFrom(i);
622
		const from2 = from1.substring(from1.indexOf("@") + 1);
623
		const cc = ae.GetExtMsgCountry(i);
624
		const cellSnd1 = row.insertCell(-1);
625
		cellSnd1.textContent = from1.substring(0, from1.indexOf("@"));
626
627
		const flag = document.createElement("abbr");
628
		flag.textContent = getCountryFlag(cc);
629
		flag.title = getCountryName(cc);
630
631
		const fromText = document.createElement("span");
632
		fromText.textContent = " " + from2;
633
634
		const cellSnd2 = row.insertCell(-1);
635
		cellSnd2.appendChild(flag);
636
		cellSnd2.appendChild(fromText);
637
	}
638
639
	row.onclick = function() {
640
		displayMsg(isInt, i);
641
	};
642
}
643
644
function getRowsPerPage() {
645
	const tbl = document.getElementById("tbl_inbox");
646
	tbl.innerHTML = "";
647
	const row = tbl.insertRow(-1);
648
	const cell = row.insertCell(-1);
649
	cell.textContent = "0";
650
651
	const rowsPerPage = Math.floor(getComputedStyle(document.getElementById("div_inbox")).height.replace("px", "") / getComputedStyle(document.querySelector("#tbl_inbox > tbody > tr:first-child")).height.replace("px", "")) - 1; // -1 allows space for 'load more'
652
	tbl.innerHTML = "";
653
	return rowsPerPage;
654
}
655
656
function addMessages() {
657
	const maxExt = ae.GetExtMsgCount();
658
	const maxInt = ae.GetIntMsgCount();
659
660
	if (maxExt + maxInt < 1) {
661
		tabs[TAB_INBOX].max = 0;
662
		return;
663
	}
664
665
	const rowsPerPage = getRowsPerPage();
666
	let skipMsgs = rowsPerPage * tabs[TAB_INBOX].cur;
667
668
	tabs[TAB_INBOX].max = Math.floor((maxExt + maxInt - 1) / rowsPerPage);
669
670
	let numExt = 0;
671
	let numInt = 0;
672
	let numAdd = 0;
673
674
	while (numAdd < rowsPerPage) {
675
		const tsInt = (numInt < maxInt) ? ae.GetIntMsgTime(numInt) : -1;
676
		const tsExt = (numExt < maxExt) ? ae.GetExtMsgTime(numExt) : -1;
677
		if (tsInt === -1 && tsExt === -1) break;
678
679
		if (tsInt !== -1 && (tsExt === -1 || tsInt > tsExt)) {
680
			if (skipMsgs > 0) skipMsgs--; else {addMsg(true, numInt); numAdd++;}
681
			numInt++;
682
		} else if (tsExt !== -1) {
683
			if (skipMsgs > 0) skipMsgs--; else {addMsg(false, numExt); numAdd++;}
684
			numExt++;
685
		}
686
	}
687
688
	if (ae.GetReadyMsgBytes() < ae.GetTotalMsgBytes()) {
689
		const inbox = document.getElementById("tbl_inbox");
690
		const row = inbox.insertRow(-1);
691
		const cell = row.insertCell(-1);
692
		cell.textContent = "Load more (" + Math.round((ae.GetTotalMsgBytes() - ae.GetReadyMsgBytes()) / 1024) + " KiB left)";
693
694
		row.onclick = function() {
695
			this.onclick = "";
696
697
			ae.Message_Browse(false, false, function(errorBrowse) {
698
				document.getElementById("tbl_inbox").style.opacity = 1;
699
700
				if (!errorBrowse) {
701
					addMessages();
702
					addUploads();
703
					addSent();
704
					if (tabs[tab].cur < tabs[tab].max) document.getElementById("btn_rght").disabled = false;
705
				} // else TODO
706
			});
707
		};
708
	}
709
}
710
711
function addUploads() {
712
	const tbl = document.getElementById("tbd_uploads");
713
	tbl.innerHTML = "";
714
715
	for (let i = 0; i < ae.GetUplMsgCount(); i++) {
716
		const row = tbl.insertRow(-1);
717
		row.setAttribute("data-msgid", ae.GetUplMsgIdHex(i));
718
719
		let cell;
720
		cell = row.insertCell(-1); cell.textContent = new Date(ae.GetUplMsgTime(i) * 1000).toISOString().slice(0, 10);
721
722
		cell = row.insertCell(-1); cell.textContent = ae.GetUplMsgTitle(i);
723
		cell.onclick = function() {displayFile(this.parentElement.rowIndex - 1);};
724
725
		cell = row.insertCell(-1); cell.textContent = (ae.GetUplMsgBytes(i) / 1024).toFixed(1);
726
727
		cell = row.insertCell(-1);
728
		if (ae.GetUplMsgIdHex(i)) {
729
			cell.innerHTML = "<button data-msgid=\"" + ae.GetUplMsgIdHex(i) + "\" type=\"button\">X</button>";
730
731
			cell.children[0].onclick = function() {
732
				const tr = this.parentElement.parentElement;
733
				ae.Message_Delete(this.getAttribute("data-msgid"), function(error) {
734
					if (!error) tr.remove();
735
					else console.log("Error " + error);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
736
				});
737
			};
738
		}
739
	}
740
}
741
742
function displayOutMsg(num) {
743
	clearDisplay();
744
	document.getElementById("midright").scroll(0, 0);
745
	document.getElementById("midright").setAttribute("data-msgid", ae.GetOutMsgIdHex(num));
746
747
	document.getElementById("btn_mdele").disabled = false;
748
	document.getElementById("btn_msave").disabled = true;
749
	document.getElementById("btn_reply").disabled = true;
750
751
	document.getElementById("midright").children[0].hidden = false;
752
	document.getElementById("midright").children[2].hidden = false;
753
754
	document.getElementById("midright").children[1].textContent = ae.GetOutMsgSubj(num);
755
	document.getElementById("midright").children[2].textContent = ae.GetOutMsgBody(num);
756
757
	document.getElementById("readmsg_dkim").style.visibility    = "hidden";
758
	document.getElementById("readmsg_hdrto").style.visibility   = "visible";
759
	document.getElementById("readmsg_hdrfrom").style.visibility = "visible";
760
	document.getElementById("readmsg_envto").style.visibility   = "visible";
761
	document.getElementById("readmsg_envfrom").style.visibility = "hidden";
762
763
	document.getElementById("readmsg_hdrfrom").textContent = ae.GetOutMsgFrom(num);
764
765
	document.getElementById("readmsg_envto").textContent = ae.GetOutMsgMxDom(num);
766
	document.getElementById("readmsg_hdrto").textContent = ae.GetOutMsgTo(num);
767
768
	const ts = ae.GetOutMsgTime(num);
769
	const tzOs = new Date().getTimezoneOffset();
770
	document.getElementById("readmsg_date").children[1].textContent = new Date((ts * 1000) + (tzOs * -60000)).toISOString().slice(0, 19).replace("T", " ");
771
772
	const isInt = ae.GetOutMsgIsInt(num);
773
	document.getElementById("readmsg_ip").style.visibility    = isInt? "hidden" : "visible";
774
	document.getElementById("readmsg_rdns").style.visibility  = /*isInt?*/ "hidden" /*: "visible"*/; // TODO
775
	document.getElementById("readmsg_tls").style.visibility   = /*isInt?*/ "hidden" /*: "visible"*/; // TODO
776
	document.getElementById("readmsg_cert").style.visibility  = /*isInt?*/ "hidden" /*: "visible"*/; // TODO
777
	document.getElementById("readmsg_greet").style.visibility = isInt? "hidden" : "visible";
778
779
	if (!isInt) {
780
//		const cc = ae.GetExtMsgCountry(num);
781
782
		document.getElementById("readmsg_ip").children[1].textContent = ae.GetOutMsgIp(num);
783
//		document.getElementById("readmsg_country").textContent = getCountryFlag(cc) + " " + getCountryName(cc);
784
//		document.getElementById("readmsg_tls").children[0].textContent = ae.GetOutMsgTLS(num);
785
		document.getElementById("readmsg_greet").children[0].textContent = ae.GetOutMsgGreet(num);
786
	}
787
788
	let flagText = "";
789
	if (!ae.GetOutMsgFlagVPad(num)) flagText += "<abbr title=\"Invalid padding\">PAD</abbr> ";
790
	if (!ae.GetOutMsgFlagVSig(num)) flagText += "<abbr title=\"Invalid signature\">SIG</abbr> ";
791
	if (ae.GetOutMsgFlagE2ee(num)) flagText += "<abbr title=\"End-to-end encrypted\">E2EE</abbr> ";
792
	document.getElementById("readmsg_flags").children[0].innerHTML = flagText.trim();
793
}
794
795
function addSent() {
796
	const tbl = document.getElementById("tbl_drbox");
797
	tbl.innerHTML = "";
798
799
	for (let i = 0; i < ae.GetOutMsgCount(); i++) {
800
		const row = tbl.insertRow(-1);
801
		row.setAttribute("data-msgid", ae.GetOutMsgIdHex(i));
802
803
		let cell;
804
		cell = row.insertCell(-1); cell.textContent = new Date(ae.GetOutMsgTime(i) * 1000).toISOString().slice(0, 10);
805
		cell = row.insertCell(-1); cell.textContent = ae.GetOutMsgSubj(i);
806
		row.onclick = function() {displayOutMsg(i);};
807
	}
808
}
809
810
function updateAddressCounts() {
811
	document.getElementById("limit_normal").textContent = (ae.GetAddressCountNormal() + "/" + ae.GetLimitNormalA(ae.GetUserLevel())).padStart(ae.GetLimitNormalA(ae.GetUserLevel()) > 9 ? 5 : 1);
812
	document.getElementById("limit_shield").textContent = (ae.GetAddressCountShield() + "/" + ae.GetLimitShieldA(ae.GetUserLevel())).padStart(ae.GetLimitShieldA(ae.GetUserLevel()) > 9 ? 5 : 1);
813
	document.getElementById("limit_total").textContent = ((ae.GetAddressCountNormal() + ae.GetAddressCountShield()) + "/" + ae.GetAddrPerUser()).padStart(5);
814
815
	const limitReached = (ae.GetAddressCountNormal() + ae.GetAddressCountShield() >= 31);
816
	document.getElementById("btn_address_create_normal").disabled = (limitReached || ae.GetAddressCountNormal() >= ae.GetLimitNormalA(ae.GetUserLevel()));
817
	document.getElementById("btn_address_create_shield").disabled = (limitReached || ae.GetAddressCountShield() >= ae.GetLimitShieldA(ae.GetUserLevel()));
818
}
819
820
function adjustLevel(pubkey, level, c) {
821
	const fs = document.getElementById("tbl_accs");
822
	fs.disabled = true;
823
824
	ae.Account_Update(pubkey, level, function(error) {
825
		fs.disabled = false;
826
827
		if (!error) {
828
			c[4].textContent = level;
829
			c[5].children[0].disabled = (level === 3);
830
			c[6].children[0].disabled = (level === 0);
831
		} else console.log("Error " + error)
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
832
	});
833
}
834
835
function addAccountToTable(i) {
836
	const tblAccs = document.getElementById("tbd_accs");
837
	const row = tblAccs.insertRow(-1);
838
	let cell;
839
	cell = row.insertCell(-1); cell.textContent = ae.Admin_GetUserPkHex(i);
840
	cell = row.insertCell(-1); cell.textContent = ae.Admin_GetUserSpace(i);
841
	cell = row.insertCell(-1); cell.textContent = ae.Admin_GetUserNAddr(i);
842
	cell = row.insertCell(-1); cell.textContent = ae.Admin_GetUserSAddr(i);
843
	cell = row.insertCell(-1); cell.textContent = ae.Admin_GetUserLevel(i);
844
845
	cell = row.insertCell(-1); cell.innerHTML = "<button type=\"button\" autocomplete=\"off\">+</button>";
846
	cell.children[0].onclick = function() {const c = this.parentElement.parentElement.cells; adjustLevel(c[0].textContent, parseInt(c[4].textContent, 10) + 1, c);};
847
	cell.children[0].disabled = (ae.Admin_GetUserLevel(i) === 3);
848
849
	cell = row.insertCell(-1); cell.innerHTML = "<button type=\"button\" autocomplete=\"off\">&minus;</button>";
850
	cell.children[0].onclick = function() {const c = this.parentElement.parentElement.cells; adjustLevel(c[0].textContent, parseInt(c[4].textContent, 10) - 1, c);};
851
	cell.children[0].disabled = (ae.Admin_GetUserLevel(i) === 0);
852
853
	cell = row.insertCell(-1); cell.innerHTML = "<button type=\"button\" autocomplete=\"off\">X</button>";
854
	cell.children[0].onclick = function() {
855
		const tr = this.parentElement.parentElement;
856
		ae.Account_Delete(tr.cells[0].textContent, function(error) {
857
			if (!error) tr.remove();
858
			else console.log("Error " + error);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
859
		});
860
	};
861
}
862
863
function reloadAccount() {
864
	// Limits
865
	const tblLimits = document.getElementById("tbl_limits");
866
	if (ae.IsUserAdmin()) {
867
		for (let i = 0; i < 4; i++) {
868
			tblLimits.rows[i].cells[1].children[0].disabled = false;
869
			tblLimits.rows[i].cells[2].children[0].disabled = false;
870
			tblLimits.rows[i].cells[3].children[0].disabled = false;
871
872
			tblLimits.rows[i].cells[1].children[0].value = ae.GetLimitStorage(i) + 1;
873
			tblLimits.rows[i].cells[2].children[0].value = ae.GetLimitNormalA(i);
874
			tblLimits.rows[i].cells[3].children[0].value = ae.GetLimitShieldA(i);
875
		}
876
	} else {
877
		const lvl = ae.GetUserLevel();
878
		tblLimits.rows[lvl].cells[1].children[0].value = ae.GetLimitStorage(lvl) + 1;
879
		tblLimits.rows[lvl].cells[2].children[0].value = ae.GetLimitNormalA(lvl);
880
		tblLimits.rows[lvl].cells[3].children[0].value = ae.GetLimitShieldA(lvl);
881
	}
882
883
	// Accounts
884
	const tblAccs = document.getElementById("tbd_accs");
885
886
	// All: Our account
887
	const row = tblAccs.insertRow(-1);
888
	let cell;
889
	cell = row.insertCell(-1); cell.textContent = ae.GetUserPkHex();
890
	cell = row.insertCell(-1); cell.textContent = Math.round(ae.GetTotalMsgBytes() / 1048576); // MiB
891
	cell = row.insertCell(-1); cell.textContent = ae.GetAddressCountNormal();
892
	cell = row.insertCell(-1); cell.textContent = ae.GetAddressCountShield();
893
	cell = row.insertCell(-1); cell.textContent = ae.GetUserLevel();
894
	cell = row.insertCell(-1); cell.innerHTML = "<button type=\"button\" autocomplete=\"off\" disabled=\"disabled\">+</button>";
895
896
	cell = row.insertCell(-1); cell.innerHTML = "<button id=\"btn_lowme\" type=\"button\" autocomplete=\"off\" disabled=\"disabled\">&minus;</button>";
897
	cell.children[0].onclick = function() {
898
		const newLevel = parseInt(row.cells[4].textContent, 10) - 1;
899
		ae.Account_Update(ae.GetUserPkHex(), newLevel, function(error) {
900
			if (!error) row.cells[4].textContent = newLevel;
901
			else console.log("Error " + error);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
902
		});
903
	};
904
905
	cell = row.insertCell(-1); cell.innerHTML = "<button id=\"btn_delme\" type=\"button\" autocomplete=\"off\" disabled=\"disabled\">X</button>";
906
	cell.children[0].onclick = function() {
907
		ae.Account_Delete(ae.GetUserPkHex(), function(error) {
908
			if (!error) row.remove();
909
			else console.log("Error " + error);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
910
		});
911
	};
912
913
	document.getElementById("txt_reg").disabled = !ae.IsUserAdmin();
914
	document.getElementById("btn_reg").disabled = !ae.IsUserAdmin();
915
	document.getElementById("chk_lowme").disabled = (ae.GetUserLevel() === 0);
916
917
	// Contacts
918
	for (let i = 0; i < ae.GetContactCount(); i++) {
919
		addContact(
920
			ae.GetContactMail(i),
921
			ae.GetContactName(i),
922
			ae.GetContactNote(i)
923
		);
924
	}
925
926
	refreshContactList();
927
928
	// Addresses
929
	for (let i = 0; i < ae.GetAddressCount(); i++) {
930
		addAddress(i);
931
	}
932
933
	updateAddressCounts();
934
	addMessages();
935
	addUploads();
936
	addSent();
937
938
	document.getElementById("btn_rght").disabled = (tabs[tab].cur === tabs[tab].max);
939
}
940
941
function deleteAddress(addr) {
942
	let btns = document.getElementById("tbl_addrs").getElementsByTagName("button");
943
	for (let i = 0; i < btns.length; i++) btns[i].disabled = true;
944
945
	let addressToDelete = -1;
946
947
	for (let i = 0; i < ae.GetAddressCount(); i++) {
948
		if (addr === ae.GetAddress(i)) {
949
			addressToDelete = i;
950
			break;
951
		}
952
	}
953
954
	if (addressToDelete === -1) return;
955
956
	ae.Address_Delete(addressToDelete, function(error1) {
957
		if (!error1) {
958
			document.getElementById("tbl_addrs").deleteRow(addressToDelete);
959
			document.getElementById("write_from").remove(addressToDelete);
960
			updateAddressCounts();
961
962
			const limitReached = (ae.GetAddressCountNormal() + ae.GetAddressCountShield() >= 31);
963
			document.getElementById("btn_address_create_normal").disabled = (limitReached || ae.GetAddressCountNormal() > ae.GetLimitNormalA(ae.GetUserLevel()));
964
			document.getElementById("btn_address_create_shield").disabled = (limitReached || ae.GetAddressCountShield() > ae.GetLimitShieldA(ae.GetUserLevel()));
965
966
			ae.Private_Update(function(error2) {
967
				if (error2) console.log("Failed to update the Private field: " + error2);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
968
969
				btns = document.getElementById("tbl_addrs").getElementsByTagName("button");
970
				for (let i = 0; i < btns.length; i++) btns[i].disabled = false;
971
			});
972
		} else {
973
			console.log("Failed to delete address: " + error1);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
974
975
			btns = document.getElementById("tbl_addrs").getElementsByTagName("button");
976
			for (let i = 0; i < btns.length; i++) btns[i].disabled = false;
977
		}
978
	});
979
}
980
981 View Code Duplication
function shieldMix(addr) {
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
982
	let newAddr = "";
983
984
	for (let i = 0; i < 16; i++) {
985
		switch (addr.charAt(i)) {
986
			case '1':
987
				newAddr += "1iIlL".charAt(Math.floor(Math.random() * 5));
988
				break;
989
			case '0':
990
				newAddr += "0oO".charAt(Math.floor(Math.random() * 3));
991
				break;
992
			case 'w':
993
				newAddr += "VvWw".charAt(Math.floor(Math.random() * 4));
994
				break;
995
			default:
996
				newAddr += (Math.random() > 0.5) ? addr.charAt(i) : addr.charAt(i).toUpperCase();
997
		}
998
	}
999
1000
	return newAddr;
1001
}
1002
1003
function addAddress(num) {
1004
	const addrTable = document.getElementById("tbl_addrs");
1005
	const row = addrTable.insertRow(-1);
1006
	const cellAddr = row.insertCell(-1);
1007
	const cellChk1 = row.insertCell(-1);
1008
	const cellChk2 = row.insertCell(-1);
1009
	const cellBtnD = row.insertCell(-1);
1010
1011
	cellAddr.textContent = ae.GetAddress(num);
1012
	cellAddr.onclick = function() {
1013
		if (cellAddr.textContent.length === 16)
1014
			navigator.clipboard.writeText(shieldMix(cellAddr.textContent) + "@" + ae.GetDomainEml());
1015
		else
1016
			navigator.clipboard.writeText(cellAddr.textContent + "@" + ae.GetDomainEml());
1017
	};
1018
1019
	cellChk1.innerHTML = ae.GetAddressAccExt(num) ? "<input type=\"checkbox\" checked=\"checked\">" : "<input type=\"checkbox\">";
1020
	cellChk2.innerHTML = ae.GetAddressAccInt(num) ? "<input type=\"checkbox\" checked=\"checked\">" : "<input type=\"checkbox\">";
1021
1022
	cellBtnD.innerHTML = "<button type=\"button\">X</button>";
1023
	cellBtnD.onclick = function() {deleteAddress(cellAddr.textContent);};
1024
1025
	const opt = document.createElement("option");
1026
	opt.value = cellAddr.textContent;
1027
	opt.textContent = cellAddr.textContent + "@" + ae.GetDomainEml();
1028
	document.getElementById("write_from").appendChild(opt);
1029
}
1030
1031
document.getElementById("btn_dele").onclick = function() {
1032
	this.blur();
1033
1034
	if (tab === TAB_WRITE) {
1035
		tabs[tab].cur = 0;
1036
		updateTab();
1037
1038
		document.querySelector("#write2_pkey > input").value = "";
1039
1040
		document.getElementById("write_recv").value = "";
1041
		document.getElementById("write_subj").value = "";
1042
		document.getElementById("write_body").value = "";
1043
1044
		document.getElementById("write_recv").readOnly = false;
1045
		document.getElementById("write_subj").readOnly = false;
1046
		document.getElementById("write_subj").setAttribute("data-replyid", "");
1047
1048
		document.getElementById("write_recv").focus();
1049
	}
1050
};
1051
1052
document.getElementById("btn_updt").onclick = function() {
1053
	const btn = this;
1054
	btn.disabled = true;
1055
	btn.blur();
1056
1057
	if (tab === TAB_INBOX) {
1058
		document.getElementById("tbl_inbox").style.opacity = 0.5;
1059
1060
		ae.Message_Browse(true, false, function(error) {
1061
			document.getElementById("tbl_inbox").style.opacity = 1;
1062
1063
			if (!error) {
1064
				addMessages();
1065
				addUploads();
1066
				btn.disabled = false;
1067
			} else {
1068
				console.log("Failed to refresh: " + error);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
1069
				btn.disabled = false;
1070
			}
1071
		});
1072
	}
1073
};
1074
1075
document.getElementById("btn_mdele").onclick = function() {
1076
	const btn = this;
1077
	btn.blur();
1078
	btn.disabled = true;
1079
1080
	const delId = document.getElementById("midright").getAttribute("data-msgid");
1081
	if (!delId) return;
1082
1083
	ae.Message_Delete(delId, function(error) {
1084
		if (!error) {
1085
			["tbl_inbox", "tbl_drbox", "tbd_uploads"].forEach(function(tbl_name) {
1086
				const tbl = document.getElementById(tbl_name);
1087
				for (let i = 0; i < tbl.rows.length; i++) {if (tbl.rows[i].getAttribute("data-msgid") === delId) tbl.deleteRow(i);}
1088
			});
1089
1090
			addMessages();
1091
			addUploads();
1092
			addSent();
1093
		} else btn.disabled = false; // TODO display error
1094
	});
1095
};
1096
1097
function refreshContactList() {
1098
	const lst = document.getElementById("contact_emails");
1099
	lst.innerHTML = "";
1100
1101
	for (let i = 0; i < ae.GetContactCount(); i++) {
1102
		const el = document.createElement("option");
1103
		el.value = ae.GetContactMail(i);
1104
		lst.appendChild(el);
1105
	}
1106
1107
	if (ae.IsUserAdmin()) {
1108
		const el = document.createElement("option");
1109
		el.value = "public";
1110
		lst.appendChild(el);
1111
	}
1112
}
1113
1114
function addContact(mail, name, note) {
1115
	const tbl = document.getElementById("tbl_ctact");
1116
	const row = tbl.insertRow(-1);
1117
	const cellMail = row.insertCell(-1);
1118
	const cellName = row.insertCell(-1);
1119
	const cellNote = row.insertCell(-1);
1120
	const cellBtnD = row.insertCell(-1);
1121
1122
	cellMail.autocapitalize = "off";
1123
	cellMail.spellcheck = false;
1124
	cellMail.inputMode = "email";
1125
1126
	cellName.autocapitalize = "words";
1127
	cellName.spellcheck = false;
1128
1129
	cellNote.autocapitalize = "off";
1130
	cellNote.spellcheck = false;
1131
1132
	cellMail.textContent = mail;
1133
	cellName.textContent = name;
1134
	cellNote.textContent = note;
1135
	cellBtnD.innerHTML = "<button type=\"button\">X</button>";
1136
1137
	cellMail.contentEditable = true;
1138
	cellName.contentEditable = true;
1139
	cellNote.contentEditable = true;
1140
1141
	cellBtnD.onclick = function() {row.remove();};
1142
}
1143
1144
document.getElementById("btn_newcontact").onclick = function() {
1145
	addContact("", "", "");
1146
};
1147
1148
document.getElementById("btn_savecontacts").onclick = function() {
1149
	while (ae.GetContactCount() > 0) {
1150
		ae.DeleteContact(0);
1151
	}
1152
1153
	for (const row of document.getElementById("tbl_ctact").rows) {
1154
		ae.AddContact(row.cells[0].textContent, row.cells[1].textContent, row.cells[2].textContent);
1155
	}
1156
1157
	refreshContactList();
1158
1159
	const btn = this;
1160
	btn.disabled = true;
1161
1162
	ae.Private_Update(function(error) {
1163
		btn.disabled = false;
1164
1165
		if (error) {
1166
			console.log("Failed contacts update: " + error);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
1167
		}
1168
	});
1169
};
1170
1171
function writeVerify() {
1172
	if (
1173
	   !document.getElementById("write_recv").reportValidity()
1174
	|| !document.getElementById("write_subj").reportValidity()
1175
	|| !document.getElementById("write_body").reportValidity()
1176
	) {tabs[TAB_WRITE].cur = 0; return;}
1177
1178
	document.getElementById("div_write_1").hidden = true;
1179
	document.getElementById("div_write_2").hidden = false;
1180
1181
	document.getElementById("write2_recv").textContent = document.getElementById("write_recv").value;
1182
	document.getElementById("write2_subj").textContent = document.getElementById("write_subj").value;
1183
	document.getElementById("write2_rply").textContent = document.getElementById("write_subj").getAttribute("data-replyid");
1184
	document.getElementById("write2_body").textContent = document.getElementById("write_body").value;
1185
1186
	if (document.getElementById("write_recv").value.indexOf("@") >= 0) {
1187
		document.getElementById("write2_from").textContent = document.getElementById("write_from").value + "@" + ae.GetDomainEml();
1188
		document.getElementById("write2_pkey").hidden = true;
1189
	} else {
1190
		document.getElementById("write2_from").textContent = document.getElementById("write_from").value;
1191
		document.getElementById("write2_pkey").hidden = (document.getElementById("write_recv").value === "public");
1192
	}
1193
1194
	document.querySelector("#write2_send > button").disabled = false;
1195
	document.getElementById("write2_btntxt").textContent = (document.getElementById("write_recv").value === "public") ? "Make" : "Send to";
1196
}
1197
1198
function updateTab() {
1199
	switch (tab) {
1200
		case TAB_INBOX:
1201
			addMessages();
1202
		break;
1203
1204
		case TAB_DRBOX:
1205
			addSent();
1206
		break;
1207
1208
		case TAB_WRITE:
1209
			if (tabs[tab].cur === 0) {
1210
				document.getElementById("div_write_1").hidden = false;
1211
				document.getElementById("div_write_2").hidden = true;
1212
				document.getElementById("write_body").focus();
1213
			} else {
1214
				writeVerify();
1215
			}
1216
		break;
1217
1218
		case TAB_NOTES:
1219
			for (let i = 0; i <= tabs[tab].max; i++) {
1220
				document.getElementById("div_notes").children[i].hidden = (i !== tabs[tab].cur);
1221
			}
1222
		break;
1223
1224
		case TAB_TOOLS:
1225
			for (let i = 0; i <= tabs[tab].max; i++) {
1226
				document.getElementById("div_tools").children[i].hidden = (i !== tabs[tab].cur);
1227
			}
1228
		break;
1229
	}
1230
1231
	document.getElementById("btn_left").disabled = (tabs[tab].cur === 0);
1232
	document.getElementById("btn_rght").disabled = (tabs[tab].cur === tabs[tab].max);
1233
}
1234
1235
document.getElementById("btn_left").onclick = function() {
1236
	tabs[tab].cur--;
1237
	if (tabs[tab].cur === 0) this.disabled = true;
1238
	if (tabs[tab].cur < tabs[tab].max) document.getElementById("btn_rght").disabled = false;
1239
	updateTab();
1240
	this.blur();
1241
};
1242
1243
document.getElementById("btn_rght").onclick = function() {
1244
	tabs[tab].cur++;
1245
	if (tabs[tab].cur === tabs[tab].max) this.disabled = true;
1246
	document.getElementById("btn_left").disabled = false;
1247
	updateTab();
1248
	this.blur();
1249
};
1250
1251
const buttons = document.querySelector("#main1 > .top").getElementsByTagName("button");
1252
for (let i = 0; i < buttons.length; i++) {
1253
	buttons[i].onclick = function() {
1254
		tab = i;
1255
1256
		for (let j = 0; j < buttons.length; j++) {
1257
			document.querySelector("#main1 > .mid").children[j].hidden = (tab !== j);
1258
			buttons[j].disabled = (tab === j);
1259
		}
1260
1261
		document.getElementById("btn_left").disabled = (tabs[tab].cur === 0);
0 ignored issues
show
Bug introduced by
The variable tab is changed as part of the for loop for example by i on line 1254. Only the value of the last iteration will be visible in this function if it is called after the loop.
Loading history...
1262
		document.getElementById("btn_rght").disabled = (tabs[tab].cur === tabs[tab].max);
1263
		document.getElementById("btn_dele").disabled = !tabs[tab].btnDele;
1264
		document.getElementById("btn_updt").disabled = !tabs[tab].btnUpdt;
1265
1266
		updateTab();
1267
	};
1268
}
1269
1270
function addressCreate(addr) {
1271
	document.getElementById("btn_address_create_normal").disabled = true;
1272
	document.getElementById("btn_address_create_shield").disabled = true;
1273
1274
	ae.Address_Create(addr, function(error1) {
1275
		if (!error1) {
1276
			ae.Private_Update(function(error2) {
1277
				updateAddressCounts();
1278
1279
				if (!error2) {
1280
					addAddress(ae.GetAddressCount() - 1);
1281
					if (addr !== "SHIELD") document.getElementById("txt_address_create_normal").value = "";
1282
				} else {
1283
					console.log("Failed to update the Private field: " + error2);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
1284
				}
1285
			});
1286
		} else {
1287
			console.log("Failed to add address: " + error1);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
1288
			updateAddressCounts();
1289
		}
1290
	});
1291
}
1292
1293
document.getElementById("btn_address_create_normal").onclick = function() {
1294
	if (ae.GetAddressCountNormal() >= ae.GetLimitNormalA(ae.GetUserLevel()) || ae.GetAddressCountNormal() + ae.GetAddressCountShield() >= 31) return;
1295
1296
	const txtNewAddr = document.getElementById("txt_address_create_normal");
1297
	if (!txtNewAddr.reportValidity()) return;
1298
1299
	addressCreate(txtNewAddr.value);
1300
};
1301
1302
document.getElementById("btn_address_create_shield").onclick = function() {
1303
	if (ae.GetAddressCountShield() >= ae.GetLimitShieldA(ae.GetUserLevel()) || ae.GetAddressCountNormal() + ae.GetAddressCountShield() >= 31) return;
1304
1305
	addressCreate("SHIELD");
1306
};
1307
1308
document.getElementById("btn_address_update").onclick = function() {
1309
	const btn = this;
1310
	btn.disabled = true;
1311
1312
	const rows = document.getElementById("tbl_addrs").rows;
1313
1314
	for (let i = 0; i < rows.length; i++) {
1315
		ae.SetAddressAccExt(i, rows[i].getElementsByTagName("input")[0].checked);
1316
		ae.SetAddressAccInt(i, rows[i].getElementsByTagName("input")[1].checked);
1317
	}
1318
1319
	ae.Address_Update(function(error) {
1320
		if (error) console.log("Address/Update failed: " + error);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
1321
		btn.disabled = false;
1322
	});
1323
};
1324
1325
1326
document.getElementById("txt_reg").onkeyup = function(event) {
1327
	if (event.key === "Enter") {
1328
		event.preventDefault();
1329
		document.getElementById("btn_reg").click();
1330
	}
1331
};
1332
1333
document.getElementById("btn_reg").onclick = function() {
1334
	const btn = document.getElementById("btn_reg");
1335
	const txt = document.getElementById("txt_reg");
1336
	if (!txt.reportValidity()) return;
1337
	btn.disabled = true;
1338
1339
	ae.Account_Create(txt.value, function(error) {
1340
		if (!error) {
1341
			addAccountToTable(ae.Admin_GetUserCount() - 1);
1342
			txt.value = "";
1343
		} // else TODO
1344
1345
		btn.disabled = false;
1346
	});
1347
};
1348
1349
document.getElementById("chk_delme").onclick = function() {document.getElementById("btn_delme").disabled = !this.checked;};
1350
document.getElementById("chk_lowme").onclick = function() {document.getElementById("btn_lowme").disabled = !this.checked;};
1351
1352
document.getElementById("btn_notepad_saveupl").onclick = function() {
1353
	const np = document.getElementById("txt_notepad");
1354
	np.disabled = true;
1355
1356
	let fname = prompt("Save as...", "Untitled");
0 ignored issues
show
Debugging Code Best Practice introduced by
The prompt UI element is often considered obtrusive and is generally only used as a temporary measure. Consider replacing it with another UI element.
Loading history...
1357
	if (!fname.endsWith(".txt")) fname += ".txt";
1358
1359
	ae.Message_Upload(fname, np.value, function(error) {
1360
		if (!error) {
1361
			np.value = "";
1362
			addUploads();
1363
			document.getElementById("tbd_accs").children[0].children[1].textContent = Math.round(ae.GetTotalMsgBytes() / 1024 / 1024);
1364
		} else {
1365
			console.log("Failed to add text: " + error);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
1366
		}
1367
1368
		np.disabled = false;
1369
	});
1370
};
1371
1372
document.getElementById("btn_upload").onclick = function() {
1373
	const btn = this;
1374
	const fileSelector = document.createElement("input");
1375
	fileSelector.type = "file";
1376
	fileSelector.click();
1377
1378
	fileSelector.onchange = function() {
1379
		btn.disabled = true;
1380
1381
		const reader = new FileReader();
1382
		reader.onload = function() {
1383
			ae.Message_Upload(fileSelector.files[0].name, new Uint8Array(reader.result), function(error) {
1384
				if (!error) {
1385
					addUploads();
1386
					document.getElementById("tbd_accs").children[0].children[1].textContent = Math.round(ae.GetTotalMsgBytes() / 1024 / 1024);
1387
				} else {
1388
					console.log("Failed upload: " + error);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
1389
				}
1390
1391
				btn.disabled = false;
1392
			});
1393
		};
1394
1395
		reader.readAsArrayBuffer(fileSelector.files[0]);
1396
	};
1397
};
1398
1399
document.getElementById("btn_pg").onclick = function() {
1400
	localStorage.greeting = document.getElementById("txt_pg").value;
1401
};
1402
1403
document.querySelector("#write2_send > button").onclick = function() {
1404
	const btn = this;
1405
	btn.disabled = true;
1406
1407
	// Public announcement
1408
	if (document.getElementById("write2_recv").textContent === "public") {
1409
		ae.Message_Public(document.getElementById("write_subj").value, document.getElementById("write_body").value, function(error) {
1410
			if (!error) {
1411
				document.getElementById("write2_btntxt").textContent = "Announced to";
1412
				document.getElementById("write_recv").value = "";
1413
				document.getElementById("write_subj").value = "";
1414
				document.getElementById("write_body").value = "";
1415
			} else {
1416
				// TODO display error
1417
				document.getElementById("write2_btntxt").textContent = "Retry making";
1418
				btn.disabled = false;
1419
			}
1420
		});
1421
1422
		return;
1423
	}
1424
1425
	// Email or internal message
1426
	document.getElementById("write2_btntxt").textContent = "Sending to";
1427
1428
	ae.Message_Create(
1429
		document.getElementById("write_subj").value,
1430
		document.getElementById("write_body").value,
1431
		document.getElementById("write_from").value,
1432
		document.getElementById("write_recv").value,
1433
		document.getElementById("write_subj").getAttribute("data-replyid"),
1434
		(document.getElementById("write2_recv").textContent.indexOf("@") > 0) ? null : sodium.from_base64(document.querySelector("#write2_pkey > input").value, sodium.base64_variants.ORIGINAL_NO_PADDING),
1435
		function(error) {
1436
			if (!error) {
1437
				document.getElementById("write2_btntxt").textContent = "Delivered to";
1438
				document.getElementById("write_recv").value = "";
1439
				document.getElementById("write_subj").value = "";
1440
				document.getElementById("write_body").value = "";
1441
			} else {
1442
				// TODO display error
1443
				document.getElementById("write2_btntxt").textContent = "Retry sending to";
1444
				btn.disabled = false;
1445
			}
1446
		}
1447
	);
1448
};
1449
1450
document.getElementById("txt_skey").onfocus = function() {
1451
	document.getElementById("greeting").textContent = localStorage.greeting;
1452
};
1453
1454
document.getElementById("txt_skey").onkeyup = function(event) {
1455
	if (event.key === "Enter") {
1456
		event.preventDefault();
1457
		document.getElementById("btn_enter").click();
1458
	}
1459
};
1460
1461
document.getElementById("btn_enter").onclick = function() {
1462
	const txtSkey = document.getElementById("txt_skey");
1463
1464
	if (txtSkey.value === "") {
1465
		ae.Reset();
1466
		document.getElementById("greeting").textContent = "Data cleared";
1467
		return;
1468
	}
1469
1470
	if (!txtSkey.reportValidity()) return;
1471
1472
	const btn = this;
1473
	btn.disabled = true;
1474
1475
	document.getElementById("txt_skey").disabled = true;
1476
	document.getElementById("txt_skey").style.background = "#233";
1477
1478
	ae.SetKeys(txtSkey.value, function(successSetKeys) {
1479
		if (successSetKeys) {
1480
			document.body.style.cursor = "wait";
1481
1482
			ae.Message_Browse(false, true, function(errorBrowse) {
1483
				document.body.style.cursor = "auto";
1484
1485
				if (!errorBrowse) {
1486
					txtSkey.value = "";
1487
					document.getElementById("div_begin").hidden = true;
1488
					document.getElementById("div_main").hidden = false;
1489
					reloadAccount();
1490
1491
					if (ae.IsUserAdmin()) {
1492
						ae.Account_Browse(function(errorAcc) {
1493
							if (!errorAcc) {
1494
								for (let i = 0; i < ae.Admin_GetUserCount(); i++) {addAccountToTable(i);}
1495
							} else {
1496
								console.log("Failed to Account_Browse: " + errorAcc);
0 ignored issues
show
Debugging Code introduced by
console.log looks like debug code. Are you sure you do not want to remove it?
Loading history...
1497
							}
1498
						});
1499
					}
1500
				} else {
1501
					document.getElementById("txt_skey").disabled = false;
1502
					document.getElementById("txt_skey").style.background = "#466";
1503
					btn.focus();
1504
1505
					document.getElementById("greeting").textContent = "Error " + errorBrowse;
1506
					btn.disabled = false;
1507
				}
1508
			});
1509
		} else {
1510
			document.getElementById("txt_skey").disabled = false;
1511
			document.getElementById("txt_skey").style.background = "#466";
1512
			txtSkey.focus();
1513
1514
			document.getElementById("greeting").textContent = "SetKeys failed";
1515
			btn.disabled = false;
1516
		}
1517
	});
1518
};
1519
1520
});
1521